package br.sc.senai.model.dao;

import static br.sc.senai.model.db.ConnectionFactory.getDBInstance;
import static br.sc.senai.model.resource.ReflectionAux.getValuesFromEntity;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import br.sc.senai.model.exception.DBException;
import br.sc.senai.model.exception.EntitiesDAOExcption;
import br.sc.senai.model.exception.GenericException;
import br.sc.senai.model.interfaces.IQuery;
import br.sc.senai.model.resource.Mensagens;

/**
 * @author jefferson
 * */

public class EntitiesDAO <T> {
	
	private static IQuery query;
	private Logger manageLog;
	private T entity;
		
	public EntitiesDAO(T pEntity) throws EntitiesDAOExcption {
		String mDirClass = "";
		try {
			manageLog = Logger.getLogger(this.getClass().getName());
			this.entity = pEntity;
			
			query = (IQuery) Class.forName(
					mDirClass = String.format("br.sc.senai.model.query.%sQuery", pEntity.getClass().getSimpleName()),
					true,
					ClassLoader.getSystemClassLoader()).newInstance();
		} catch (InstantiationException e) {
			throw new EntitiesDAOExcption(String.format(Mensagens.ERRO_INESPERADO, "Instanciar EntitiesDAO."), e);
		} catch (IllegalAccessException e) {
			throw new EntitiesDAOExcption(String.format(Mensagens.ERRO_INESPERADO, "Erro de acesso à classe."), e);
		} catch (ClassNotFoundException e) {
			throw new EntitiesDAOExcption(String.format(Mensagens.ERRO_INESPERADO, "Classe " + mDirClass + " não existe. Verifica novamente o nome ou o caminho do arquivo!"), e);
		}
	}

	public Integer add(T pEntity) throws EntitiesDAOExcption {
		Connection mConn = null;
		try {
			String mQuery = String.format(this.query.getQueryInsert(), getValuesFromEntity(pEntity));
			this.manageLog.log(Level.INFO, String.format("---> Gerado sql para inserir no bando: \n%s", mQuery));
			
			Statement mStatement = (mConn = getDBInstance().getConnection()).createStatement();
			this.manageLog.log(Level.INFO, "\n---> Conectado no banco.\n---> Criado um Statement.\n---> Irá executar comando e retornar o id.");
			
			return mStatement.executeUpdate(mQuery, 1);
		} catch (GenericException e) {
			throw new EntitiesDAOExcption(String.format(Mensagens.ERRO_INESPERADO, "Adicionar novo registro."), e);
		} catch (SQLException e) {
			throw new EntitiesDAOExcption(String.format(Mensagens.ERRO_INESPERADO, "Adicionar novo registro."), e);
		} finally{
			try {
				getDBInstance().desconectar(mConn);
			} catch (DBException e) {
				throw new EntitiesDAOExcption(e.getMessage(), e);
			}
		}
	}
	
	public void update(T pEntity) throws EntitiesDAOExcption{
		Connection mConn = null;
		try {
			String mQuery = String.format(this.query.getQueryUpdateById(), getValuesFromEntity(pEntity));
			this.manageLog.log(Level.INFO, String.format("---> Gera sql de alteração: \n%s", mQuery));
			
			Statement mStatement = (mConn = getDBInstance().getConnection()).createStatement();
			this.manageLog.log(Level.INFO, "\n---> Conectado no banco.\n---> Criado um Statement.\n---> Irá executar comando.");
			mStatement.executeUpdate(mQuery);
		} catch (GenericException e) {
			throw new EntitiesDAOExcption(String.format(Mensagens.ERRO_INESPERADO, "Alterar registro."), e);
		} catch (SQLException e) {
			throw new EntitiesDAOExcption(String.format(Mensagens.ERRO_INESPERADO, "Alterar registro."), e);
		} finally{
			try {
				getDBInstance().desconectar(mConn);
			} catch (DBException e) {
				throw new EntitiesDAOExcption(e.getMessage(), e);
			}
		}
	}
	
	public void delete(T pEntity) throws EntitiesDAOExcption{
		Connection mConn = null;
		try {
			String mQuery = String.format(this.query.getQueryDeleteById(), pEntity.getClass().getMethod("getId").invoke(pEntity));
			this.manageLog.log(Level.INFO, String.format("---> Gera sql de exclusão: \n%s", mQuery));
			
			Statement mStatement = (mConn = getDBInstance().getConnection()).createStatement();
			this.manageLog.log(Level.INFO, "\n---> Conectado no banco.\n---> Criado um Statement.\n---> Irá executar comando.");
			mStatement.executeUpdate(mQuery);
		} catch (GenericException e) {
			throw new EntitiesDAOExcption(String.format(Mensagens.ERRO_INESPERADO, "Alterar registro."), e);
		} catch (Exception e) {
			throw new EntitiesDAOExcption(String.format(Mensagens.ERRO_INESPERADO, "Alterar registro."), e);
		} finally{
			try {
				getDBInstance().desconectar(mConn);
			} catch (DBException e) {
				throw new EntitiesDAOExcption(e.getMessage(), e);
			}
		}
	}
	
	public List<T> loadAll() throws EntitiesDAOExcption{
		Connection mConn = null;
		try {
			String mQuery = this.query.getQuerySelectAll();
			this.manageLog.log(Level.INFO, String.format("---> Gerado sql para consulta no bando: \n%s", mQuery));
			
			Statement mStatement = (mConn = getDBInstance().getConnection()).createStatement();
			
			List<T> mList = createEntity(mStatement.executeQuery(mQuery));
			Integer mLength = mList == null ? 0 : mList.size();
			this.manageLog.log(Level.INFO, String.format("---> Criado lista de objetos do tipo '%s', tamanho da lista é %s", this.entity.getClass().getName(), mLength));
			
			return mList;
		} catch (Exception e) {
			throw new EntitiesDAOExcption(String.format(Mensagens.ERRO_INESPERADO, "Realizar consutla."), e);
		} finally{
			try {
				getDBInstance().desconectar(mConn);
			} catch (DBException e) {
				throw new EntitiesDAOExcption(e.getMessage(), e);
			}
		}
	}
	
	public T getById(Integer pId) throws EntitiesDAOExcption {
		try {
			return getBySql(String.format(query.getQuerySelectById(), pId));
		} catch (GenericException e) {
			throw new EntitiesDAOExcption(e.getMessage());
		}
	}
	
	private List<T> createEntity(ResultSet pResultSet) throws EntitiesDAOExcption{
		try {
			T mEntity = null;
			List<T> mListEntity = new ArrayList<T>();
			Field[] mArrayFields  = entity.getClass().getDeclaredFields();
			Field mField = entity.getClass().getSuperclass().getDeclaredField("id");
			List<Field> mListFields = new ArrayList<Field>();
			
			mListFields.add(mField);
			
			for (Field field : mArrayFields) {
				mListFields.add(field);
			}
						
			while(pResultSet.next()){
				mEntity = (T) entity.getClass().newInstance();
				
				for (Field field : mListFields) {
					field.setAccessible(true);
					Object mValue = pResultSet.getObject(field.getName());
					
					String mBegin = field.getName().substring(0, 1).toUpperCase();
					String mEnd = field.getName().substring(1);
					
					String mAppObjAttrType = field.getGenericType().toString();
					if (mAppObjAttrType.contains("sql.Date") && 
							java.sql.Timestamp.class.getSimpleName().equals(mValue.getClass().getSimpleName())){
						mValue = new java.sql.Date(((Timestamp) mValue).getTime());
					} else if (mAppObjAttrType.contains("util.Date") && 
							java.sql.Timestamp.class.getSimpleName().equals(mValue.getClass().getSimpleName())){
						mValue = new java.util.Date(((Timestamp) mValue).getTime());
					}
					
					Class[] mParam = new Class[]{mValue.getClass()};
					
					entity.getClass().getMethod(String.format("set%s", String.format("%s%s", mBegin, mEnd)), mParam).invoke(mEntity, mValue);
				}
				mListEntity.add(mEntity);
			}
			
			return mListEntity;
		} catch (Exception e) {
			throw new EntitiesDAOExcption(String.format(Mensagens.ERRO_INESPERADO, "Criar entidade apartir de um ResultSet."), e);
		}
	}
	
	public List<T> loadBySql(String pQuery) throws EntitiesDAOExcption{
		Connection mConn = null;
		try {
			this.manageLog.log(Level.INFO, String.format("---> Gerado sql para consulta no bando: \n%s", pQuery));
			
			Statement mStatement = (mConn = getDBInstance().getConnection()).createStatement();
			
			List<T> mList = createEntity(mStatement.executeQuery(pQuery));
			Integer mLength = mList == null ? 0 : mList.size();
			this.manageLog.log(Level.INFO, String.format("---> Criado lista de objetos do tipo '%s', tamanho da lista é %s", this.entity.getClass().getName(), mLength));
			
			return mList;
		} catch (Exception e) {
			throw new EntitiesDAOExcption(String.format(Mensagens.ERRO_INESPERADO, "Realizar consutla."), e);
		} finally{
			try {
				getDBInstance().desconectar(mConn);
			} catch (DBException e) {
				throw new EntitiesDAOExcption(e.getMessage(), e);
			}
		}
	}
	
	public T getBySql(String pQuery) throws EntitiesDAOExcption{
		Connection mConn = null;
		try {
			this.manageLog.log(Level.INFO, String.format("---> Gerado sql para consulta no bando: \n%s", pQuery));
			
			Statement mStatement = (mConn = getDBInstance().getConnection()).createStatement();
			
			List<T> mList = createEntity(mStatement.executeQuery(pQuery));
			Integer mLength = mList == null ? 0 : mList.size();
			this.manageLog.log(Level.INFO, String.format("---> Criado lista de objetos do tipo '%s', tamanho da lista é %s", this.entity.getClass().getName(), mLength));
			
			if (mList == null || mList.size() > 1)
				throw new GenericException(String.format(Mensagens.ERRO_INESPERADO, "O foi encontrado mais de um registro."));
			
			return mList.get(0);
		} catch (GenericException e) {
			throw new EntitiesDAOExcption(e.getMessage(), e);
		} catch (Exception e) {
			throw new EntitiesDAOExcption(String.format(Mensagens.ERRO_INESPERADO, "Realizar consutla."), e);
		} finally{
			try {
				getDBInstance().desconectar(mConn);
			} catch (DBException e) {
				throw new EntitiesDAOExcption(e.getMessage(), e);
			}
		}
	}
	
}